/**
@author		Jong Min, Lee
@date		  01/15/2014
@breif		Viewer controller class

copyright(c) Tae Young Soft. All right reserved. 2014
*/
#include "stdafx.h"
#include "Taurus_Viewer/ViewerController.h"
#include <algorithm>
#include "Taurus_Common/IAppGlobalInstanceSet.h"
#include "Taurus_Common/Monitor.h"
#include "Taurus_Common/MonitorAdmin.h"
#include "Taurus_Util/Errors.h"
#include "Taurus_Viewer/VGlobalInstanceSet.h"
#include "Taurus_Viewer/MonitorWnd.h"
#include "Taurus_Viewer/Logger.h"
#include "Taurus_Viewer/ToolbarProperty.h"
#include "Taurus_Viewer/VisibleToolbar.h"
#include "Taurus_Viewer/StudyWorkSpace.h"
#include "Taurus_Viewer/StudyViewController.h"
#include "Taurus_Viewer/HiddenToolbar.h"
#include "Taurus_Viewer/StudyTab.h"
#include "Taurus_Viewer/StudyTabProperty.h"
#include "Taurus_Viewer/resource.h"

CViewerController* CViewerController::m_pInstance = NULL;

CViewerController::CViewerController()
{
  init();
}

CViewerController::~CViewerController()
{
  destroy();
}

CViewerController* CViewerController::getInstance()
{
  if(m_pInstance == NULL)
    _TRY_ALLOC(m_pInstance, CViewerController);

  return m_pInstance;
}

DWORD CViewerController::doConfigure()
{
  //configure monitor setting.
  DWORD dwError = doConfigureMonitors();
  if(dwError != ERROR_SUCCESS)
  {
    destroy();
    return dwError;
  }

  //initialize monitor property
  dwError = initMonitorGroupPropety();
  if(dwError != ERROR_SUCCESS)
  {
    destroy();
    return dwError;
  }

  //initialize study view layout property
  dwError = initStudyViewLayoutProperty();
  if(dwError != ERROR_SUCCESS)
  {
    destroy();
    return dwError;
  }

  //configure toolbar setting
  dwError = doConfigureToolbars();
  if(dwError != ERROR_SUCCESS)
  {
    destroy();
    return dwError;
  }
  
  dwError = doConfigureStudyTabs();
  if(dwError != ERROR_SUCCESS)
  {
    destroy();
    return dwError;
  }

  initialArrange();

  CVGlobalInstanceSet* pVGSet = CVGlobalInstanceSet::getInstance();
  IAppGlobalInstanceSet* pAppGSet = pVGSet->getAppGlobalInstanceSet();
  CMonitorAdmin* pMAdmin = pAppGSet->getMonitorAdmin();

  const CMonitor* pWorklistMonitor = pMAdmin->getInitWorklistMonitor();
  for(auto pos = m_arrMonitorWnds.begin(), e = m_arrMonitorWnds.end();
      pos != e; ++pos)
  {
    const CMonitor* pParnetMonitor = (*pos)->getMonitor();
    if(pParnetMonitor != pWorklistMonitor)
    {
      (*pos)->ShowWindow(SW_SHOW);
      (*pos)->UpdateWindow();
    }
  }

  return ERROR_SUCCESS;
}

DWORD CViewerController::setMonitorGroup(const TCHAR* group)
{
  if(group == NULL) return ERROR_INVALID_PARAMETER;

  CString newGroup = group;
  CString currentGroup = m_theMonitorGroups.convertToStr();
  if(currentGroup != newGroup)
  {
    return setMonitorGroup(currentGroup, newGroup, FALSE, TRUE, TRUE);
  }

  return ERROR_SUCCESS;
}

DWORD CViewerController::setSWSLayout(unsigned int monitorIndex, 
                                      const C2DLayout& swsLayout)
{
  if(!m_theStudyViewLayout.isAvailable(swsLayout)) return ERROR_INVALID_PARAMETER;

  if(monitorIndex >= m_arrMonitorWnds.size()) return ERROR_INVALID_PARAMETER;

  unsigned int groupIndex = 0;
  const CMonitorGroup* group = m_theMonitorGroups.findItem(m_arrMonitorWnds[monitorIndex], &groupIndex);
  if(group->getNumItems() >= 2 && swsLayout.getCount() > 1)
    return ERROR_INVALID_PARAMETER;

  C2DLayout curSWSLayout = *(m_theStudyViewLayout.getLayout(monitorIndex));

  if(curSWSLayout == swsLayout) return ERROR_SUCCESS;

  DWORD error = createAndAddSWS(groupIndex, swsLayout);
  if(error != ERROR_SUCCESS) return error;

  unsigned int numMonitors = group->getNumItems();
  for(unsigned int i = 0; i < numMonitors; ++i)
  {
    unsigned int monitorIndex = group->getItem(i)->getMonitorIndex();
    m_theStudyViewLayout.setLayout(monitorIndex, swsLayout);
  }

  error = attachSWSsToMonitors(TRUE);
  if(error != ERROR_SUCCESS)
  {
    for(unsigned int i = 0; i < numMonitors; ++i)
    {
      unsigned int monitorIndex = group->getItem(i)->getMonitorIndex();
      m_theStudyViewLayout.setLayout(monitorIndex, curSWSLayout);
    }
    return error;
  }

  CStudyWorkSpace* focusedSWS = findFocusedSWS(NULL);
  if(focusedSWS == NULL)
  {
    unsigned int uid = 0;
    if(group->getNumItems() > 0 && group->getNumSWSs(0) > 0)
      uid = group->getSWS(0, 0)->theUniqueID;
    else
      uid = (*m_theSWSs.begin())->theUniqueID;

    setFocusedSWS(uid, TRUE);
  }

  onSWSLayoutChanged();
  return ERROR_SUCCESS;
}

DWORD CViewerController::ShowViewer(const CMonitor* pMonitor)
{
  auto pos = find_if(m_arrMonitorWnds.cbegin(), m_arrMonitorWnds.cend(),
    [&pMonitor](CMonitorWnd* pMonitorWnd)
  {
    return (pMonitor == pMonitorWnd->getMonitor());
  });

  if(pos != m_arrMonitorWnds.end())
  {
    (*pos)->ShowWindow(SW_SHOW);
    (*pos)->UpdateWindow();
  }
  return ERROR_SUCCESS;
}

DWORD CViewerController::doConfigureMonitors()
{
  CVGlobalInstanceSet* pVGSet = CVGlobalInstanceSet::getInstance();
  IAppGlobalInstanceSet* pAGSet = pVGSet->getAppGlobalInstanceSet();
  CMonitorAdmin* pMAdmin = pAGSet->getMonitorAdmin();

  unsigned nNumMonitors = pMAdmin->getNumVisibleMonitors();
  _TRY_BEGIN
  m_arrMonitorWnds.reserve(nNumMonitors);
  _CATCH_ALL
  TAURUS_VIEWER_ERROR("CViewerController::doConfigureMonitors() : "
  << "fails to reserve memory for monitor windows");
  return ERROR_NOT_ENOUGH_MEMORY;
  _CATCH_END

  CWnd* pMainWnd = ::AfxGetMainWnd();
  DWORD dwError = ERROR_SUCCESS;
  unsigned int monitorIndex = 0;
  pMAdmin->forEachVisibleMonitors(
    [this, &dwError, &pMainWnd, &monitorIndex](const CMonitor* pMonitor)
  {
    CMonitorWnd* pMonitorWnd = NULL;
    _TRY_ALLOC(pMonitorWnd, CMonitorWnd);
    if(pMonitorWnd == NULL)
    {
      TAURUS_VIEWER_ERROR("CViewerController::doConfigureMonitors(): "
        << "fails to allocate memory for monitor window");
      dwError = ERROR_NOT_ENOUGH_MEMORY;
      return false;
    }

    CRect rcMonitor = pMonitor->getWorkRect();
    CMonitorWnd::CreateParams stParams;
    stParams.theParentMonitor = pMonitor;
    stParams.theMonitorIndex  =  monitorIndex++;

    if(!pMonitorWnd->create(rcMonitor, pMainWnd, stParams))
    {
      dwError = ::GetLastError();
      dwError = (dwError == ERROR_SUCCESS ? ERROR_UNKNOWN : dwError);
      TAURUS_VIEWER_ERROR("CViewerController::doConfigureMonitors(): "
        << "fails to create monitor window");
      _DELETE_WND(pMonitorWnd);
      return false;
    }

    this->m_arrMonitorWnds.push_back(pMonitorWnd);
    return true;
  });

  return dwError;
}

DWORD CViewerController::initMonitorGroupPropety()
{
  IAppGlobalInstanceSet* pAGSet = CVGlobalInstanceSet::getInstance()->getAppGlobalInstanceSet();
  CMonitorAdmin* pMA = pAGSet->getMonitorAdmin();
  const TCHAR* pszLayout = pMA->getMonitorLayoutString(0);
  return m_theMonitorGroups.groupMonitors(pszLayout, m_arrMonitorWnds);
}

DWORD CViewerController::initStudyViewLayoutProperty()
{
  return m_theStudyViewLayout.doConfigure();
}

DWORD CViewerController::doConfigureToolbars()
{
  CToolbarProperty* pTBProp = CToolbarProperty::getInstance();

  unsigned nNumToolboxes = pTBProp->getNumToolboxes();
  std::vector<const CToolboxData*> arrToolboxes;
  if(nNumToolboxes > 0)
  {
    _TRY_BEGIN
    arrToolboxes.reserve(nNumToolboxes);
    _CATCH_ALL
    return ERROR_NOT_ENOUGH_MEMORY;
    _CATCH_END
  }

  pTBProp->forEachToolboxes(
    [&arrToolboxes](const CToolboxData* pToolbox)
  {
    arrToolboxes.push_back(pToolbox);
    return true;
  });

  unsigned nNumMonitors = m_arrMonitorWnds.size();
  //reserve space for pointer to visible toolbars
  _TRY_BEGIN
  m_arrVisibleToolbars.reserve(nNumMonitors);
  m_theHiddenToolbars.reserve(nNumMonitors);
  _CATCH_ALL
  return ERROR_NOT_ENOUGH_MEMORY;
  _CATCH_END

  DWORD dwError = ERROR_SUCCESS;
  for(unsigned i = 0; i < nNumMonitors; i++)
  {
    CVisibleToolbar* pToolbar = nullptr;
    _TRY_ALLOC(pToolbar, CVisibleToolbar);
    if(pToolbar == NULL)
    {
      dwError = ERROR_NOT_ENOUGH_MEMORY;
      break;
    }

    pToolbar->onChangedCurSelMLayout += &m_theTBMLayoutHandler;
    pToolbar->onChangedCurSelSLayout += &m_theTBSLayoutHandler;
    pToolbar->onClickedHide += &m_theTBHideHandler;

    CMonitorWnd* pMonitorWnd = m_arrMonitorWnds[i];
    const CMonitor* pMonitor = pMonitorWnd->getMonitor();

    CVisibleToolbar::CreateParams stParams;
    stParams.theResType = pMonitor->getResolutionType();
    stParams.theMonitorIndex = i;
    if(!pToolbar->CreateEx(0,
                           NULL,
                           NULL,
                           WS_CHILD | WS_VISIBLE,
                           CRect(0, 0, 0, 0),
                           pMonitorWnd,
                           IDC_VISIBLE_TOOLBAR,
                           &stParams))
    {
      dwError = ::GetLastError();
      _DELETE_WND(pToolbar);
      if(dwError == ERROR_SUCCESS) dwError = ERROR_UNKNOWN;
      break;
    }

    m_arrVisibleToolbars.push_back(pToolbar);

    dwError = pToolbar->openToolboxes(arrToolboxes.data(), arrToolboxes.size(), FALSE);
    if(dwError != ERROR_SUCCESS) break;

    CHiddenToolbar* hiddenTB = NULL;
    _TRY_ALLOC(hiddenTB, CHiddenToolbar);
    if(hiddenTB == NULL)
    {
      dwError = ERROR_NOT_ENOUGH_MEMORY;
      break;
    }

    hiddenTB->onSelectItem += &m_theHiddenTBSelectItemHandler;

    CHiddenToolbar::CreateParam stHiddenParams;
    stHiddenParams.theResType = pMonitor->getResolutionType();
    stHiddenParams.theMonitorIndex = i;
    if(!hiddenTB->CreateEx(0,
                           NULL,
                           NULL,
                           WS_CHILD,
                           CRect(0,0,0,0),
                           pMonitorWnd,
                           IDC_HIDDEN_TOOLBAR,
                           &stHiddenParams))
    {
      dwError = ::GetLastError();
      _DELETE_WND(hiddenTB);
      if(dwError == ERROR_SUCCESS) dwError = ERROR_UNKNOWN;
      break;
    }
    m_theHiddenToolbars.push_back(hiddenTB);
  }

  if(dwError != ERROR_SUCCESS)
  {
    for(unsigned i = 0; i < m_arrVisibleToolbars.size(); i++)
    {
      CVisibleToolbar* pToolbar = m_arrVisibleToolbars[i];
      _DELETE_WND(pToolbar);
    }
    m_arrVisibleToolbars.clear();

    for(unsigned i = 0; i < m_theHiddenToolbars.size(); ++i)
      _DELETE_WND(m_theHiddenToolbars[i]);
    m_theHiddenToolbars.clear();
  }

  return ERROR_SUCCESS;
}

DWORD CViewerController::doConfigureStudyTabs()
{
  //import study tab property
  CStudyTabProperty* TP = CStudyTabProperty::getInstance();
  DWORD error = TP->importProperty();
  if(error != ERROR_SUCCESS) return error;

  unsigned int numMonitors = m_arrMonitorWnds.size();
  try
  {
    m_theStudyTabs.reserve(numMonitors);
  }
  catch(...)
  {
    return ERROR_NOT_ENOUGH_MEMORY;
  }

  for(unsigned int i = 0; i < numMonitors; ++i)
  {
    CStudyTab* tab = NULL;
    _TRY_ALLOC(tab, CStudyTab);
    if(tab == NULL)
    {
      error = ERROR_NOT_ENOUGH_MEMORY;
      break;
    }

    tab->showScrollButtons(TRUE);
    tab->showCloseButton(TRUE);
    CMonitorWnd* monitorWnd = m_arrMonitorWnds[i];
    CStudyTab::CreateParams param;
    param.theResType = monitorWnd->getMonitor()->getResolutionType();
    param.theMonitorIndex = i;
    if(!tab->CreateEx(0, NULL, NULL, WS_CHILD, CRect(0, 0, 0, 0), monitorWnd, IDC_STUDY_TAB + i, (LPVOID)&param))
    {
      error = ::GetLastError();
      if(error == ERROR_SUCCESS) error = ERROR_UNKNOWN;
      _DELETE_WND(tab);
      break;
    }

    m_theStudyTabs.push_back(tab);
  }

  if(error != ERROR_SUCCESS)
  {
    for(unsigned int i = 0; i < m_theStudyTabs.size(); ++i)
    {
      _DELETE_WND(m_theStudyTabs[i]);
    }
    m_theStudyTabs.clear();
  }

  return error;
}

void CViewerController::init()
{
  m_theTBMLayoutHandler.set(this, &CViewerController::monitorLayout_Changed);
  m_theTBSLayoutHandler.set(this, &CViewerController::studyViewLayout_Changed);
  m_theTBHideHandler.set(this, &CViewerController::hideButton_Clicked);
  m_theHiddenTBSelectItemHandler.set(this, &CViewerController::selectItem);
}

void CViewerController::destroy()
{
  //clean monitor group property
  m_theMonitorGroups.removeAllGroups();

  //destroy and remove all visible toolbar
  removeAllVisibleToolbars();

  //destroy and remove all hidden toolbar
  removeAllHiddenToolbars();

  //detroy and remove all study tabs
  removeAllStudyTabs();

  //destroy and remove all monitor windows
  removeAllMonitorWnds();
}

void CViewerController::initialArrange()
{
  CToolbarProperty* TBProp = CToolbarProperty::getInstance();
  ToolbarPosition tbPos = TBProp->getPresetToolbarPos();

  CStudyTabProperty* tabProp = CStudyTabProperty::getInstance();

  unsigned numMonitors = m_arrMonitorWnds.size();
  for(unsigned int mindex = 0; mindex < numMonitors; ++mindex)
  {
    CRect monitorRect;
    CMonitorWnd* monitorWnd = m_arrMonitorWnds[mindex];
    monitorWnd->GetClientRect(monitorRect);

    if(tbPos == ToolbarPositionLeft || tbPos == ToolbarPositionRight)
    {
      BOOL visibilityTB = TRUE;
      TBProp->getTBVisibility(mindex, visibilityTB);
      CRect tbRect;
      if(visibilityTB)
      {
        m_theHiddenToolbars[mindex]->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_HIDEWINDOW);
        m_arrVisibleToolbars[mindex]->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_SHOWWINDOW);
        m_arrVisibleToolbars[mindex]->changePosition(tbPos, monitorRect, FALSE);
        m_arrVisibleToolbars[mindex]->GetWindowRect(&tbRect);
        monitorWnd->ScreenToClient(&tbRect);
      }
      else
      {
        UINT uFlags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_HIDEWINDOW;
        m_arrVisibleToolbars[mindex]->SetWindowPos(NULL, 0, 0, 0, 0, uFlags);
        uFlags = SWP_NOREDRAW | SWP_SHOWWINDOW;
        tbRect = monitorRect;
        tbRect.right = tbRect.left + CToolbarLayout::getHiddenToobarSize(monitorWnd->getMonitor()->getResolutionType());
        m_theHiddenToolbars[mindex]->SetWindowPos(NULL, 
                                                  tbRect.left,
                                                  tbRect.top,
                                                  tbRect.Width(),
                                                  tbRect.Height(),
                                                  uFlags);
      }

      CRect tabRect(0, 0, 0, 0);
      UINT uFlags = SWP_NOREDRAW;
      BOOL visibleTab = TRUE;
      tabProp->getVisibility(mindex, visibleTab);
      if(visibleTab)
      {
        uFlags |= SWP_SHOWWINDOW;
        tabRect.SubtractRect(monitorRect, tbRect); 
        unsigned int tabHeight = tabProp->getTabHeight(monitorWnd->getMonitor()->getResolutionType());
        tabRect.bottom = tabRect.top + tabHeight;
      }
      else
      {
        uFlags |= SWP_HIDEWINDOW;
      }

      CStudyTab* tab = m_theStudyTabs[mindex];
      tab->SetWindowPos(NULL, tabRect.left, tabRect.top, tabRect.Width(), tabRect.Height(), uFlags);
    }
    else
    {
      CRect tabRect(0,0,0,0);
      UINT uFlags = SWP_NOREDRAW;
      BOOL visibleTab = TRUE;
      tabProp->getVisibility(mindex, visibleTab);
      if(visibleTab)
      {
        uFlags |= SWP_SHOWWINDOW;
        int tabHeight = tabProp->getTabHeight(monitorWnd->getMonitor()->getResolutionType());
        tabRect = monitorRect;
        tabRect.bottom = tabRect.top + tabHeight;
      }
      else
      {
        uFlags |= SWP_HIDEWINDOW;
      }

      CStudyTab* tab = m_theStudyTabs[mindex];
      tab->SetWindowPos(NULL, tabRect.left, tabRect.top, tabRect.Width(), tabRect.Height(), uFlags);

      CRect tbRect(0,0,0,0);
      tbRect.SubtractRect(monitorRect, tabRect);
      CVisibleToolbar* visibleTB = m_arrVisibleToolbars[mindex];
      visibleTB->changePosition(tbPos, tbRect, FALSE);
    }
  }

  CString strMLayout = m_theMonitorGroups.convertToStr();
  setMonitorGroup(strMLayout, strMLayout, FALSE, FALSE, FALSE);
}

DWORD CViewerController::arrangeControls(const unsigned int monitorIndex)
{
  //check whether monitor index is legal or not
  if(monitorIndex >= m_arrMonitorWnds.size()) return ERROR_INVALID_PARAMETER;

  //get toolbar position and visibility
  CToolbarProperty* tbProp = CToolbarProperty::getInstance();
  ToolbarPosition toolbarPos = tbProp->getPresetToolbarPos();
  BOOL tbVisible = TRUE;
  tbProp->getTBVisibility(monitorIndex, tbVisible);

  //get study tab visibility
  CStudyTabProperty* theStudyTabProp = CStudyTabProperty::getInstance();
  BOOL visibleStudyTab = TRUE;
  theStudyTabProp->getVisibility(monitorIndex, visibleStudyTab);

  //get monitor window rect
  CMonitorWnd* monitorWnd = m_arrMonitorWnds[monitorIndex];
  CRect monitorRect;
  monitorWnd->GetClientRect(&monitorRect);

  CRect workRect = monitorRect;
  CRect temp;
  CRect tbRect;
  CRect tabRect;
  if(toolbarPos == ToolbarPositionLeft || toolbarPos == ToolbarPositionRight)
  {
    //if toolbar should be visible, show visible toolbar and hide hidden toolbar
    if(tbVisible)
    {
      m_theHiddenToolbars[monitorIndex]->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOMOVE | SWP_HIDEWINDOW);
      m_arrVisibleToolbars[monitorIndex]->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_SHOWWINDOW);
      m_arrVisibleToolbars[monitorIndex]->changePosition(toolbarPos, workRect, FALSE);
      m_arrVisibleToolbars[monitorIndex]->GetWindowRect(&tbRect);
      monitorWnd->ScreenToClient(&tbRect);
      temp = workRect;
      workRect.SubtractRect(workRect, tbRect);
    }
    //if toolbar should be hidden, show hidden toolbar and show visible toolbar
    else
    {
      UINT uFlags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_HIDEWINDOW;
      m_arrVisibleToolbars[monitorIndex]->SetWindowPos(NULL, 0, 0, 0, 0, uFlags);
      uFlags = SWP_NOREDRAW | SWP_SHOWWINDOW;
      tbRect = workRect;
      tbRect.right = tbRect.left + CToolbarLayout::getHiddenToobarSize(monitorWnd->getMonitor()->getResolutionType());
      m_theHiddenToolbars[monitorIndex]->SetWindowPos(NULL,
                                                      tbRect.left,
                                                      tabRect.top,
                                                      tbRect.Width(),
                                                      tbRect.Height(),
                                                      uFlags);
      temp = workRect;
      workRect.SubtractRect(workRect, tbRect);
    }

    UINT uFlags = SWP_NOREDRAW;
    if(visibleStudyTab)
    {
      uFlags |= SWP_SHOWWINDOW;
      unsigned int tabHeight = theStudyTabProp->getTabHeight(monitorWnd->getMonitor()->getResolutionType());
      tabRect = workRect;
      tabRect.bottom = tabRect.top + tabHeight;
      temp = workRect;
      workRect.SubtractRect(workRect, tabRect);
    }
    else
    {
      uFlags |= SWP_HIDEWINDOW;
    }

    m_theStudyTabs[monitorIndex]->SetWindowPos(NULL, tabRect.left, tabRect.top, tabRect.Width(), tabRect.Height(), uFlags);
  }
  else
  {
    UINT uFlags = SWP_NOREDRAW;
    if(visibleStudyTab)
    {
      uFlags |= SWP_SHOWWINDOW;
      tabRect = workRect;
      unsigned int tabHeight = theStudyTabProp->getTabHeight(monitorWnd->getMonitor()->getResolutionType());
      tabRect.bottom = tabRect.top + tabHeight;
      temp = workRect;
      workRect.SubtractRect(workRect, tabRect);
    }
    else
    {
      uFlags |= SWP_HIDEWINDOW;
    }
    m_theStudyTabs[monitorIndex]->SetWindowPos(NULL, tabRect.left, tabRect.top, tabRect.Width(), tabRect.Height(), uFlags);

    if(tbVisible)
    {
      m_theHiddenToolbars[monitorIndex]->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_HIDEWINDOW);
      m_arrVisibleToolbars[monitorIndex]->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_SHOWWINDOW);
      m_arrVisibleToolbars[monitorIndex]->changePosition(toolbarPos, workRect, FALSE);
      m_arrVisibleToolbars[monitorIndex]->GetWindowRect(&tbRect);
      monitorWnd->ScreenToClient(&tbRect);
      temp = workRect;
      workRect.SubtractRect(workRect, tbRect);
    }
    else
    {
      m_arrVisibleToolbars[monitorIndex]->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_HIDEWINDOW);
      tabRect = workRect;
      tabRect.bottom = tabRect.top + CToolbarLayout::getHiddenToobarSize(monitorWnd->getMonitor()->getResolutionType());
      m_theHiddenToolbars[monitorIndex]->SetWindowPos(NULL, tbRect.left, tabRect.top, tabRect.Width(), tabRect.Height(), SWP_NOREDRAW | SWP_SHOWWINDOW);
      temp = workRect;
      workRect.SubtractRect(workRect, tabRect);
    }
  }

  const C2DLayout* swsLayout = m_theStudyViewLayout.getLayout(monitorIndex);

  const CMonitorGroup* group = m_theMonitorGroups.findItem(m_arrMonitorWnds[monitorIndex], NULL);
  unsigned int index = 0;
  group->getIndex(m_arrMonitorWnds[monitorIndex], index);
  ASSERT(group->getNumSWSs(index) == swsLayout->getCount());

  double dfUnitWidth = 0.0;
  double dfUnitHeight = 0.0;

  dfUnitHeight = workRect.Height() / static_cast<double>(swsLayout->theRows);
  dfUnitWidth  = workRect.Width() /  static_cast<double>(swsLayout->theCols);
  for(short rows = 0; rows < swsLayout->theRows; ++rows)
  {
    for(short cols = 0; cols < swsLayout->theCols; ++cols)
    {
      CRect rect;
      rect.left = workRect.left + round(cols * dfUnitWidth);
      rect.top =  workRect.top + round(rows * dfUnitHeight);
      rect.right = workRect.left + round((cols + 1) * dfUnitWidth);
      rect.bottom = workRect.top + round((rows + 1) * dfUnitHeight);
      unsigned int swsIndex = (swsLayout->theCols * rows) + cols;
      const CStudyWorkSpace* sws = group->getSWS(index, swsIndex);
      sws->theViewController->MoviewStudyView(monitorWnd, rect, FALSE);
    }
  }

  return ERROR_SUCCESS;
}

void CViewerController::removeAllVisibleToolbars()
{
  unsigned nNumToolbars = m_arrVisibleToolbars.size();
  for(unsigned i = 0; i < nNumToolbars; ++i)
  {
    _DELETE_WND(m_arrVisibleToolbars[i]);
  }
  m_arrVisibleToolbars.clear();
}

void CViewerController::removeAllHiddenToolbars()
{
  unsigned numTBs = m_theHiddenToolbars.size();
  for(unsigned i = 0; i < numTBs; ++i)
    _DELETE_WND(m_theHiddenToolbars[i]);
  m_theHiddenToolbars.clear();
}

void CViewerController::removeAllMonitorWnds()
{
  unsigned nNumMonitors = m_arrMonitorWnds.size();
  for(unsigned i = 0; i < nNumMonitors; ++i)
  {
    CMonitorWnd* pMonitorWnd = m_arrMonitorWnds[i];
    _DELETE_WND(pMonitorWnd);
  }
  m_arrMonitorWnds.clear();
}

void CViewerController::removeAllStudyTabs()
{
  unsigned int numTabs = m_theStudyTabs.size();
  for(unsigned int i = 0; i < numTabs; ++i)
  {
    _DELETE_WND(m_theStudyTabs[i]);
  }
  m_theStudyTabs.clear();
}

DWORD CViewerController::createSWS(CStudyWorkSpace*& outSWS)
{
  CStudyWorkSpace* newSWS = NULL;
  _TRY_ALLOC(newSWS, CStudyWorkSpace);
  if(newSWS == NULL) return ERROR_NOT_ENOUGH_MEMORY;

  CStudyViewController* newSC = NULL;
  _TRY_ALLOC(newSC, CStudyViewController);
  if(newSC == NULL)
  {
    _DELETE(newSWS);
    return ERROR_NOT_ENOUGH_MEMORY;
  }

  newSWS->theViewController = newSC;

  outSWS = newSWS;
  return ERROR_SUCCESS;
}

DWORD CViewerController::createAndAddSWS(CStudyWorkSpace*& outSWS)
{
  CStudyWorkSpace* newSWS = NULL;
  DWORD error = createSWS(newSWS);
  if(error != ERROR_SUCCESS) return error;

  try
  {
    m_theSWSs.push_back(newSWS);
  }
  catch(...)
  {
    _DELETE(newSWS);
    return ERROR_NOT_ENOUGH_MEMORY;
  }

  outSWS = newSWS;
  return ERROR_SUCCESS;
}

DWORD CViewerController::createAndAddSWS(const unsigned int groupIndex, const C2DLayout& layout)
{
  if(groupIndex >= m_theMonitorGroups.getNumItems()) return ERROR_INVALID_PARAMETER;

  const CMonitorGroup* group = m_theMonitorGroups.getItem(groupIndex);
  if(group->getNumItems() >= 2)
  {
    if(layout.getCount() > 1)
      return ERROR_INVALID_PARAMETER;
  }

  unsigned numSWSNeeded = calcNumSWSNeeded(groupIndex, layout);
  if(numSWSNeeded == 0) return ERROR_SUCCESS;

  DWORD error = ERROR_SUCCESS;
  CStudyWorkSpace* newSWS = NULL;
  for(unsigned int i = 0; i < numSWSNeeded; ++i)
  {
    error = createAndAddSWS(newSWS);
    if(error != ERROR_SUCCESS) break;
  }

  return error;
}

unsigned int CViewerController::calcNumSWSNeeded(unsigned int groupIndex, 
                                                 const C2DLayout& layout)
{
  int numNewSWS = 0;
  int numCurretSWS = static_cast<int>(m_theSWSs.size());
  unsigned int numGrops = m_theMonitorGroups.getNumItems();
  for(unsigned int i = 0; i < numGrops; ++i)
  {
    const CMonitorGroup* group = m_theMonitorGroups.getItem(i);
    unsigned int numMonitors = group->getNumItems();
    if(numMonitors >= 2)
      numNewSWS++;
    else
    {
      for(unsigned int k = 0; k < numMonitors; ++k)
      {
        if(groupIndex == i)
          numNewSWS += layout.getCount();
        else
        {
          const CMonitorWnd* monitorWnd = group->getItem(k);
          unsigned int monitorInex = monitorWnd->getMonitorIndex();
          const C2DLayout* l = m_theStudyViewLayout.getLayout(monitorInex);
          numNewSWS += l->getCount();
        }
      }
    }
  }

  numNewSWS -= numCurretSWS;
  if(numNewSWS < 0)
    return 0;
  else
    return static_cast<unsigned>(numNewSWS);
}

CStudyWorkSpace* CViewerController::findSWS(const unsigned int uid, unsigned int* indexInList)
{
  unsigned int index = 0;

  auto res = std::find_if(m_theSWSs.begin(), m_theSWSs.end(),
    [&uid, &index](CStudyWorkSpace* sws)
  {
    index++;
    return (sws->theUniqueID == uid);
  });

  if(res == m_theSWSs.end()) return NULL;

  if(indexInList != NULL) *indexInList = index;
  return (*res);
}

DWORD CViewerController::setMonitorGroup(const CString& oldLayout, 
                                         const CString& newLayout, 
                                         BOOL changeSWSLayout, 
                                         BOOL moveFocuseSWSFront, 
                                         BOOL redraw)
{
  DWORD error = m_theMonitorGroups.groupMonitors(newLayout, m_arrMonitorWnds);
  if(error != ERROR_SUCCESS) return error;

  BOOL changedSWSLayout = FALSE;
  unsigned int monitorIndex = 0;
  unsigned int numGroups = m_theMonitorGroups.getNumItems();
  C2DLayout swsLayout;
  for(unsigned int groupIndex = 0; groupIndex < numGroups; ++groupIndex)
  {
    const CMonitorGroup* group = m_theMonitorGroups.getItem(groupIndex);
    unsigned int numMonitors = group->getNumItems();
    swsLayout = CStudyViewLayoutProperty::theDefault;

    for(unsigned i = 0; i < numMonitors; ++i)
    {
      const CMonitorWnd* monitorWnd = group->getItem(i);
      if(numMonitors >= 2)
      {
        swsLayout.theRows = swsLayout.theCols = 1;
        m_theStudyViewLayout.setLayout(monitorWnd->getMonitorIndex(), swsLayout);
        changedSWSLayout = TRUE;
      }
      else if(changeSWSLayout)
      {
        m_theStudyViewLayout.setLayout(monitorWnd->getMonitorIndex(), swsLayout);
        changedSWSLayout = TRUE;
      }
      else
      {
        swsLayout = *m_theStudyViewLayout.getLayout(monitorWnd->getMonitorIndex());
      }
    }

    error = createAndAddSWS(groupIndex, swsLayout);
    if(error != ERROR_SUCCESS) return error;
  }

  CStudyWorkSpace* focusedSWS = findFocusedSWS(NULL);
  if(moveFocuseSWSFront && focusedSWS != NULL)
  {
    error = moveFocusedAndOccupiedSWSToFront();
    if(error != ERROR_SUCCESS) return error;
  }

  error = attachSWSsToMonitors(redraw);
  if(error != ERROR_SUCCESS) return error;

  if(focusedSWS == NULL && m_theSWSs.size() > 0)
  {
    unsigned int uid = (*m_theSWSs.begin())->theUniqueID;
    setFocusedSWS(uid, redraw);
  }

  onMonitorGroupChanged();
  if(changedSWSLayout) onSWSLayoutChanged();

  return ERROR_SUCCESS;
}

void CViewerController::onMonitorGroupChanged()
{
  //update monitor layout combo box of visible toolbar
  unsigned int numToolbars = m_arrVisibleToolbars.size();
  for(unsigned int i = 0; i < numToolbars; ++i)
  {
    CVisibleToolbar* toolbar = m_arrVisibleToolbars[i];
    toolbar->updateCurSelML();
  }
}

void CViewerController::onSWSLayoutChanged()
{
  //update study view layout combo box of visible toolbar
  unsigned int numToolbars = m_arrVisibleToolbars.size();
  for(unsigned int i = 0; i < numToolbars; ++i)
  {
    m_arrVisibleToolbars[i]->updateCurSelSL();
  }
}

DWORD CViewerController::attachSWSsToMonitors(BOOL redraw)
{
  //first of all, detach monitors from all study view workspaces
  for(auto pos = m_theSWSs.begin(), endPos = m_theSWSs.end(); pos != endPos; ++pos)
  {
    CStudyWorkSpace* sws = (*pos);
    if(sws->theViewController != NULL) 
      sws->theViewController->detachMonitors(FALSE);
  }

  const CMonitorWnd** monitorWnds = NULL;
  CRect* workAreas = NULL;
  _TRY_ALLOC(monitorWnds, const CMonitorWnd*[m_arrMonitorWnds.size()])
  _TRY_ALLOC(workAreas, CRect[m_arrMonitorWnds.size()])
  if(monitorWnds == NULL || workAreas == NULL)
  {
    _DELETE_ARR(monitorWnds)
    _DELETE_ARR(workAreas)
    return ERROR_NOT_ENOUGH_MEMORY;
  }
  unsigned int numMonitors = 0;
  unsigned int numWoreArea = 0;

  m_theMonitorGroups.clearSWSGroups();

  auto posSWS = m_theSWSs.begin();
  unsigned int numGroups = m_theMonitorGroups.getNumItems();
  for(unsigned int groupIndex = 0; groupIndex < numGroups; ++groupIndex)
  {
    const CMonitorGroup* group = m_theMonitorGroups.getItem(groupIndex);
    unsigned int numMonitorsInGroup = group->getNumItems();
    if(numMonitorsInGroup >= 2)
    {
      CStudyWorkSpace* sws = (*posSWS);
      ++posSWS;

      for(unsigned monitorIndex = 0; monitorIndex < numMonitorsInGroup; ++monitorIndex)
      {
        const CMonitorWnd* monitorWnd = group->getItem(monitorIndex);
        const C2DLayout* layout = m_theStudyViewLayout.getLayout(monitorWnd->getMonitorIndex());
        ASSERT(layout->theRows == 1 && layout->theCols == 1);

        monitorWnds[numMonitors++] = monitorWnd;
        CRect rcWork = calcSWSWorkArea(monitorWnd->getMonitorIndex());
        workAreas[numWoreArea++] = rcWork;
      }

      if(sws->theViewController != NULL)
        sws->theViewController->attachMonitors(monitorWnds, workAreas, numMonitors, FALSE);

      for(unsigned monitorIndex = 0; monitorIndex < numMonitors; ++monitorIndex)
        m_theMonitorGroups.addSWS(groupIndex, monitorIndex, *sws);

      if(redraw)
      {
        for(unsigned i = 0; i < numMonitors; ++i)
        {
          ::InvalidateRect(monitorWnds[i]->GetSafeHwnd(), &(workAreas[i]), FALSE);
        }
      }
      numMonitors = numWoreArea = 0;
    }//if(numMonitors >= 2)
    else
    {
      double dfUnitWidth = 0.0;
      double dfUnitHeight = 0.0;

      const CMonitorWnd* monitorWnd = group->getItem(0);
      monitorWnds[numMonitors++] = monitorWnd;
      CRect rcWork = calcSWSWorkArea(monitorWnd->getMonitorIndex());
      const C2DLayout* layout = m_theStudyViewLayout.getLayout(monitorWnd->getMonitorIndex());
      dfUnitHeight = rcWork.Height() / static_cast<double>(layout->theRows);
      dfUnitWidth = rcWork.Width() / static_cast<double>(layout->theCols);
      
      for(int rows = 0; rows < layout->theRows; ++rows)
      {
        for(int cols = 0; cols < layout->theCols; ++cols)
        {
          CStudyWorkSpace* sws = (*posSWS);
          ++posSWS;

          CRect swsArea;
          swsArea.left = rcWork.left + round(cols * dfUnitWidth);
          swsArea.top  = rcWork.top  + round(rows * dfUnitHeight);
          swsArea.right = rcWork.left + round((cols + 1) * dfUnitWidth);
          swsArea.bottom = rcWork.top + round((rows + 1) * dfUnitHeight);

          workAreas[numWoreArea++] = swsArea;
          if(sws->theViewController != NULL)
            sws->theViewController->attachMonitors(monitorWnds, workAreas, numMonitors, FALSE);

          m_theMonitorGroups.addSWS(groupIndex, 0, *sws);

          if(redraw)
          {
            for(unsigned i = 0; i < numMonitors; ++i)
            {
              ::InvalidateRect(monitorWnds[i]->GetSafeHwnd(), &(workAreas[i]), FALSE);
            }
          }

          numWoreArea = 0;
        }
      }
      numMonitors = 0;
    }
  }// for(unsigned int groupIndex = 0; groupIndex < numGroups; ++groupIndex)

  _DELETE_ARR(monitorWnds)
  _DELETE_ARR(workAreas)
  return ERROR_SUCCESS;
}

CStudyWorkSpace* CViewerController::findFocusedSWS(unsigned int* indexInList)
{
  unsigned int index = 0;
  auto res = std::find_if(m_theSWSs.begin(), m_theSWSs.end(),
    [&index](CStudyWorkSpace* sws)
  {
    return (sws->theViewController->doesAttachMonitors() &&
            sws->hasFocus());
  });

  if(res == m_theSWSs.end()) return NULL;

  if(indexInList != NULL) *indexInList = index;

  return (*res);
}

DWORD CViewerController::moveFocusedAndOccupiedSWSToFront()
{
  unsigned int indexFocsedSWS = 0;
  CStudyWorkSpace* focusSWS = findFocusedSWS(&indexFocsedSWS);
  if(focusSWS == NULL) return ERROR_FOCUSED_SWS_NOT_FOUND;

  std::list<CStudyWorkSpace*> resortedSWSs;
  std::list<CStudyWorkSpace*> tempSWSs;
  try
  {
    tempSWSs.insert(tempSWSs.end(), m_theSWSs.begin(), m_theSWSs.end());
    auto pos = tempSWSs.begin();
    std::advance(pos, indexFocsedSWS);
    resortedSWSs.push_front(*pos);
    ++pos;
    auto endPos = tempSWSs.end();
    for(; pos != endPos; ++pos)
    {
      if((*pos)->isOccupied()) continue;

      resortedSWSs.push_back(*pos);
    }

    endPos = tempSWSs.begin();
    std::advance(endPos, indexFocsedSWS);
    for(pos = tempSWSs.begin(); pos != endPos; ++pos)
    {
      if((*pos)->isOccupied()) continue;

      resortedSWSs.push_back(*pos);
    }

    for(pos = tempSWSs.begin(), endPos = tempSWSs.end(); pos != endPos; ++pos)
    {
      if((*pos)->isOccupied() || (*pos) == focusSWS) continue;

      resortedSWSs.push_back(*pos);
    }

    pos = tempSWSs.begin();
    endPos = tempSWSs.end();
    auto swsPos = m_theSWSs.begin();
    while(pos != endPos)
    {
      *swsPos = *pos;
      ++pos;
      ++swsPos;
    }
  }
  catch(...)
  {
    return ERROR_NOT_ENOUGH_MEMORY;
  }

  return ERROR_SUCCESS;
}

CRect CViewerController::calcSWSWorkArea(const unsigned int monitorIndex) const
{
  CRect swsRect(0, 0, 0, 0);
  if(monitorIndex >= m_arrMonitorWnds.size()) return swsRect;

  CMonitorWnd* monitorWnd = m_arrMonitorWnds[monitorIndex];
  monitorWnd->GetClientRect(&swsRect);

  CRect rcTB(0,0,0,0);
  BOOL visibleTB = TRUE;
  CToolbarProperty::getInstance()->getTBVisibility(monitorIndex, visibleTB);
  if(visibleTB)
  {
    m_arrVisibleToolbars[monitorIndex]->GetWindowRect(&rcTB);

  }
  else
  {
    m_theHiddenToolbars[monitorIndex]->GetWindowRect(&rcTB);
  }

  monitorWnd->ScreenToClient(rcTB);
  CRect temp = swsRect;
  swsRect.SubtractRect(temp, rcTB);

  CRect tabRect(0,0,0,0);
  CStudyTabProperty* TP = CStudyTabProperty::getInstance();
  BOOL visibleTab = TRUE;
  TP->getVisibility(monitorIndex, visibleTab);
  if(visibleTab)
  {
    m_theStudyTabs[monitorIndex]->GetWindowRect(&tabRect);
  }
  monitorWnd->ScreenToClient(tabRect);
  temp = swsRect;
  swsRect.SubtractRect(temp, tabRect);

  return swsRect;
}

void CViewerController::setFocusedSWS(const unsigned int uid, BOOL redraw)
{
  CStudyWorkSpace* sws = NULL;
  for(auto pos = m_theSWSs.begin(), end = m_theSWSs.end(); pos != end; ++pos)
  {
    sws = *pos;
    if(sws->theUniqueID == uid)
    {
      sws->setFocus(TRUE, redraw);
    }
    else
    {
      sws->setFocus(FALSE, redraw);
    }
  }
}

void CViewerController::monitorLayout_Changed(const CVisibleToolbar* visibleTB, void* parameter)
{
  const TCHAR* layout = reinterpret_cast<const TCHAR*>(parameter);
  DWORD error = setMonitorGroup(layout);
  if(error != ERROR_SUCCESS)
  {
    auto pos = find_if(m_arrVisibleToolbars.begin(), m_arrVisibleToolbars.end(),
      [&visibleTB](CVisibleToolbar* tb)
    {
      return visibleTB == tb;
    });

    if(pos != m_arrVisibleToolbars.end())
      (*pos)->updateCurSelML();
  }

}

void CViewerController::studyViewLayout_Changed(const CVisibleToolbar* visibleTB, 
                                                void* parameter)
{
  const C2DLayout* layout = reinterpret_cast<const C2DLayout*>(parameter);
  DWORD error = setSWSLayout(visibleTB->getMonitorIndex(), *layout);
  if(error != ERROR_SUCCESS)
    const_cast<CVisibleToolbar*>(visibleTB)->updateCurSelSL();
}

void CViewerController::hideButton_Clicked(const CVisibleToolbar* visibleTB, void* parameter)
{
  unsigned int monitorIndex = visibleTB->getMonitorIndex();
  CToolbarProperty::getInstance()->setTBVisibility(monitorIndex, FALSE);
  arrangeControls(monitorIndex);
  m_arrMonitorWnds[monitorIndex]->Invalidate(TRUE);
}

void CViewerController::selectItem(const CHiddenToolbar* hiddenTB, void* parameter)
{
  unsigned int monitorIndex = hiddenTB->getMonitorIndex();
  CToolbarProperty* toolbarProperty = CToolbarProperty::getInstance();
  toolbarProperty->setTBVisibility(monitorIndex, TRUE);
  arrangeControls(monitorIndex);
  m_arrMonitorWnds[monitorIndex]->Invalidate(TRUE);
}